fix(portable): correct the Copilot, CodeWhale, VS Code, and Hermes client records - #732
Conversation
…ient records Copilot CLI records .plugin/plugin.json as the one manifest location its published order puts ahead of the emitted root, and narrows its mcp row to the listed stdio server the probe proved. CodeWhale's mcp row names the remote endpoint, header, secret-URL, redirect, and capabilities.network_hosts restrictions its plugin boundary imposes, and where that declaration has to ride. VS Code's chat.pluginLocations action takes the register role, since it loads the directory in place; Cline keeps install, because its action copies the tree. Hermes records the new repository install source for a Git owner/repository install that leaves the plugin disabled.
🦋 Changeset detectedLatest commit: d9cfb26 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
…lure to their sources The published file-locations table covers the manifest order alone: skills default to skills/ and MCP configuration is .mcp.json or .github/mcp.json, so .mcp.json is recorded as taking the MCP surface and the emitted root mcp.json stays proven only by the probe that ran without it. A CodeWhale bundle whose remote endpoint has no matching capabilities.network_hosts fails validation rather than loading with a narrowed MCP surface, and a register action is now refused against anything but a local directory.
ScriptedAlchemy
left a comment
There was a problem hiding this comment.
Updated-head review — be5eef842c9e5b7240177f3eed468aa3b28e6005
Reviewed the initial seven-file diff at 57405f9307 and the additional correction commit be5eef842c that arrived during the review. One P2 discovery-claim correction remains; the other requested record changes are materially addressed. Source, test and first-party documentation review only; no native clients or repository tests were executed by this reviewer.
P2 — A winning manifest can redirect components; do not promise that it shadows only metadata
Files: portable-1.0.0.json, clients.copilot-cli.discovery.shadowedBy; install/surface.ts, clientLine; install-surface.test.ts.
The record marks .plugin/plugin.json as taking only manifest. The renderer and new tests then promise that the winning manifest leaves the other root components readable, including the sentence uses it for manifest and still reads the rest.
The official reference's component-path fields allow skills to select different directories and mcpServers to select another file or inline definitions. Its $schema field also determines whether the selected document opts into spec semantics. The file-location precedence order does not establish that an arbitrary higher-priority manifest preserves the lower portable manifest's component interpretation.
A concrete negative fixture is a composite root with its ordinary portable plugin.json, skills/ and mcp.json, plus this higher-priority .plugin/plugin.json and the referenced alternate components:
{
"name": "override",
"skills": "alternate-skills/",
"mcpServers": "alternate-mcp.json"
}The current record/rendering continues to claim the portable root's skill/MCP files on the basis of presence alone. This is a source/document-derived counterexample; I have not executed that fixture through Copilot. The documented overrides are enough to invalidate the unconditional promise.
Minimal correction: when a winning manifest has not been inspected/proven to preserve those bindings, withhold or qualify the affected component and placeholder claims. Reuse the existing shadow/qualification model; do not add a universal discovery interpreter or assume all foreign files shadow every surface. Keep a genuine per-file .mcp.json override separate from manifest-selected component paths. Preserve the correct lower precedence of .claude-plugin/plugin.json behind the root manifest.
Add an emitted-winning-manifest case to the rendered-output tests, not just a hypothetical sentence and a lower-priority Claude-manifest fixture. Test that an uninspected/overriding selected manifest cannot produce a guaranteed Reads: skills, mcp.json claim. Make the generated English/Chinese reference say the same thing.
Corrections now present — do not redo them
- Hermes is a repository source with disabled-by-default installation retained.
- VS Code is registration in place, while Cline's copy remains installation.
- Copilot's evidence distinguishes a listed stdio configuration from executed/authenticated transport support.
- CodeWhale's remote limits and missing network-host declaration now explicitly describe validation failure/whole-bundle inactivity, rather than silently promising a working degraded connection.
- The additional commit adds
.mcp.jsonhandling, rejects non-localregisteractions, fixes the source anchor, and changes the changeset reference to #732. - Inventory-conditioned notes, the single capability validator, and shared generated reference remain the right architecture. No new native adapter is implied by these records. Aider/Jules remain excluded.
The PR's recorded full local gate and four-inventory tests are useful evidence, but they should be refreshed/identified for the final correction head. The earlier hosted query was queued/cancelled, not a reproduced code failure. This review does not add a requirement to wait for hosted CI instead of the repository's accepted local gates, and does not authorize a merge.
…all copies The agent-plugins tier sentence said every such client installs the bundle, which contradicted the register action VS Code actually publishes; the recorded action now carries that distinction alone. The generated reference says an install copies the artifact or the one component the client recognizes, since a skills-tier install copies only that component. The inventory matrix asserts CodeWhale's endpoint, header, secret-URL and redirect limits, its inactive- bundle consequence, and Copilot's unspawned-server placeholder row.
Closes #728.
Four record corrections on the pinned portable
clientsblock, plus the rendered output they feed. No adapter, registry, or discovery interpreter changed; nothing about what the compiler writes changed.Corrections
discovery.shadowedBynow records the two locations its published file-locations table puts ahead of the emitted tree, per surface:.plugin/plugin.jsonformanifest(the manifest order is.plugin/plugin.json,plugin.json,.github/plugin/plugin.json,.claude-plugin/plugin.json, so the emitted root still wins over the Claude manifest — asserted against the claude+portable composite) and.mcp.jsonformcp(the table publishes MCP configuration as.mcp.json,.github/mcp.json; the emitted rootmcp.jsonis proven only by the 1.0.83 probe, which ran with no.mcp.jsonpresent). Itsmcprow isdegraded: the probe listed one stdio server, and no streamable-http server was registered, launched, or authenticated. The local-install deprecation and the unspawned-serverplaceholdersrow stay visible.mcpreason now covers the whole plugin boundary from the pinned MCP guide and the same commit'sdocs/PLUGIN_BUNDLES.md: HTTPS-or-loopback endpoints with no user information, query, or fragment; literal headers rejected in favor ofenv_headers/bearer_token_env_var; redirects held to the reviewed origin; andcapabilities.network_hostsmatching the normalized endpoint host set exactly. That declaration rides inextensions["net.codewhale"], which this projection writes only from an authoredportable.extensions, and a remote server emitted without it is a validation error — "an active bundle must be … free of validation errors" — so the record says the bundle stays inactive there rather than loading with a narrowed surface. No new portable field, no relaxed trust policy.chat.pluginLocationscarries the already-supportedregisterrole, matching the local-plugin documentation ("register it with thechat.pluginLocationssetting …falseto keep it registered but disabled"), so the generated instructions printRegister:. Cline is asserted in the same test and unchanged: itscp -Raction really copies, soinstallstays. The validator now refuses aregisteranchor against any non-local-directory source.install.sourceis the newrepositoryvalue: the plugin guide separateshermes plugins install <name>(index name, resolved to repo @ pinned ref) fromhermes plugins install user/repo(from Git; "Explicitowner/repoor Git-URL identifiers never touch the index"). The recorded command keeps--no-enable, so the install still leaves the plugin disabled, and no local-directory form is claimed.The install-source enum genuinely lacked Git, so it gained one value (
repository) — the smallest compatible metadata addition. Non-local install prose names its source (Install from a marketplace …/Install from a Git repository …), theagent-pluginstier sentence says a client loads the bundle (the recorded action alone reports whether it copies or registers), and the generated host reference gains an install-source column in both locales.Tests
packages/agent-bundle/tests/portable-adapter.test.ts: a record test holding each correction to its pinned source (both shadowed surfaces, the quoted file-locations rows, stdio-only evidence, CodeWhale's remote restrictions, register vs install role, repository source), plus validator coverage thatrepositoryis accepted,gitis refused, and aregisteraction against arepositorysource is refused.packages/agent-bundle/tests/install-surface.test.ts: a four-row inventory matrix (no component, skills only, MCP only, skills and MCP) asserting the whole rendered line per client — tier sentence, paths this build wrote, action role, source,Not loadedlist, both.plugin/plugin.jsonand.mcp.jsonhypotheticals — and asserting the MCP narrowings (Copilot's stdio subset and unspawned server, CodeWhale's endpoint/header/secret-URL/redirect clauses and inactive-bundle consequence) appear only when the build wrotemcp.json. The composite test asserts Copilot still reads the root manifest with.claude-plugin/plugin.jsonpresent, handsmcpto the build's.mcp.json, and prints neither MCP-document narrowing there.pnpm docs:site:build: install-source column,repositoryfor Hermes,registerfor VS Code,installfor Cline,.plugin/plugin.json (manifest)and.mcp.json (mcp)shadow cells, both intro rewrites.Local gate
Run on this branch, which contains current
origin/main(b5cf7575e0):pnpm build— passpnpm typecheck— passpnpm lint— pass (1483 files)pnpm test:unit— pass, 4439 passed / 0 failed / 6 skipped (a first run under a machine load average of 300 timed out in seven unrelated timing-sensitive tests —codex-plugin-validation,event-ipc,install,mcp-probe-service,uninstall, twoweb-host-routes-unit; re-run clean once load dropped)pnpm docs:site:build— pass, language parity checked, 0 broken links / 28329 anchorsinstall-surface,portable-adapter,adapter-capability-states,adapter-metadataNo native client was executed for this change; the corrections are document review over pinned sources.
Deslop
Deslop: claude-opus-5, 3 edits (helper reduced to the source sentence it prints, CodeWhale reason held to the upstream wording, remote-form quote pinned in evidence instead of paraphrased in the reason).
Self-review
Reviewers:
claude-fable-5-1-thinking-high(change-risk-reviewer, two rounds) andgpt-5.6-sol-max(two rounds).Fable, round 1:
mcp.json, and the composite assertion locked in an unprovenmcp.jsonread beside.mcp.json— fixed: the evidence quotes the table's own rows, scopes the rootmcp.jsonread to the probe that ran without.mcp.json, and the record gained the.mcp.json→mcpshadow; the composite assertion now expectsplugin.json,skills.registerwith a non-local source would render as an install — fixed: validator guard plus a negative test.(#732).#plugin-contributed-mcp.Sol, round 1:
agent-pluginstier sentence said "installs this bundle", contradicting theregisteraction — fixed: "loads this bundle as one plugin".installcopies the artifact, though a skills-tier install copies one component — fixed in both locales.packages/**/tests/**, so reachingwebsite/plugins/generated-reference.tsneeds a test-only export ofrenderHostsor a cross-workspace import;website/docs/*/reference/hosts.mdis generated and gitignored, and AGENTS.md designatespnpm docs:site:buildas its gate. That gate passes, and both locales' rendered rows were verified directly.Round 2: Fable confirmed all five findings resolved with no new risk from the tier rename or the new shadow. Sol confirmed 1, 2 and 4 fixed and asked for one more composite assertion (Copilot's
placeholdersnarrowing suppressed once.mcp.jsontakesmcp) — added ind9cfb26af4.